一次事件会触发两次中断?
有人说在MCU的开发应用过程中遇到过一次中断事件触发两次中断的奇怪事情。有这样的事吗?应该说有真有假,这里以STM32为例来聊聊该话题。
所谓假的,就是指基于误会以为一次事件触发了两次甚至多次中断。比方按键事件没有做好消抖处理,或者中断请求标志位没有被及时清零等。顺便说下,对于STM32芯片而言,如果中断请求标志没有被清零会没完没了的循环进相应中断服务程序。
这里重点聊聊真的,即一次中断事件进入两次中断服务程序,的确有机会碰到。偶尔也有人反映类似问题,比方做UART通信时,一个空闲事件进入两次空闲中断,感觉相关标志没法清除;有人通过定时器触发SPI传输,一个定时器事件竟然进入两次中断连续给SPI数据寄存器赋值两次。
发生这种一次触发事件进入两次中断的情况时,一般有个非常明显的特征,那就是在中断服务程序里对中断请求标志的清零代码往往放在中断服务程序的最末尾。我们不妨弄个具体的实例感受下。
下面以一个定时器更新中断为例。我让定时器工作在基于向上计数的单脉冲PWM模式,即启动计数器后,当发生溢出产生更新事件时即告停止。那么每次启动定时器后按理有且只有一次进入更新中断服务程序。我在中断服务程序里放个计数变量,统计进入中断的次数。我这里使用STM32F4的开发板测试的。
先看看中断服务程序里清除中断请求标志的代码不是放在最后一行的情况。其中变量counterX用来统计进入中断服务程序次数。
这次测试结果没问题,一次更新事件对应进入一次中断服务程序。我将上面的中断服务程序稍微调整下代码前后顺序,让清除中断请求位的代码放在最后,再看看下面结果。
嗯?counterX结果变为2了,一次触发事件怎么进了两次中断服务程序呢?!
这时不同的人往往会有不同的判断或结论。比方中断请求标志一次清不掉啊;同样的写法别的系列或型号却可以,认为太莫名其妙啦!【其实,到底是不是完全相同的写法只是感觉,就像我上面的写法不细究的话也可以说是一样的写法】,或者说芯片很奇葩啊云云。
怎么会这样呢?原因就在于那行清除中断请求位的代码放在最后,在第一次退出中断服务程序时该请求位尚未完成被清零的状态。程序指令执行速度越快,这种可能性就越高。既然该中断请求位依然保持置1的有效状态,经硬件触发再次进入中断服务程序就顺理成章了。
有人会问,我在退出中断服务程序之前不是已经做了中断请求位的清零操作吗?怎么没有立即生效呢?再怎么“立即”也是需要时间的,程序指令的执行完毕和指令执行后的状态改变并不一定同步。比方你到包子铺去跟老板说买3个馒头,老板满口应诺后,你不能立即扭头就走啊。他还需要点时间来处理,不然一辈子都买不到3个馒头。具体结合到stm32芯片,程序执行是基于哈佛结构的流水线形式,前面代码执行时依然可以执行后序的指令代码。
谈到这里,有人或许想到在清除中断请求位的代码后面加上一句内存屏蔽指令,即DSB。应该说加这个DSB指令是有效的,即该指令前的所有内存访问指令执行完毕后才执行后序指令代码。不过,一般来讲,在这个地方用不着它,我们只须注意别将清除中断请求位的代码放在服务程序的末尾,稍微给清零操作留点实现时间。就像上面打比方买馒头一样,给老板一点为你取馒头的时间就行。
也许有人会说,我中断服务程序里就只需做中断请求位清零这一件事怎么办呢?那你就随便在清零操作代码后面随便一两行无关紧要的代码也行,确保不发生1次事件进两次中断即可。
刚才前面说了,当清除中断请求位的代码放在服务程序最后时,程序指令执行速度越快,一次触发事件进入两次中断服务程序的可能性就越高。我们不妨看看下面基于STM32H7系列的一段中断服务程序代码。是TIM3的更新中断服务程序,截图里的两行代码为中断服务程序的最末两行。注意,清除中断标志的代码没有在最末一行。
其基本功能就是每进一次更新中断,先清中断标志,然后给SPI数据寄存器赋值令其发送一个16位数据。显然,结合我们前面的分析,如果代码这样写一般来讲是不太可能发生一次事件触发2次中断的,事实上当程序代码在FLASH里运行时也的确没有任何问题。
但当将中断服务程序放到RAM里,比方放到DTCM里去运行时发生了功能异常。结果变成了每次更新事件发送的数据不是16位而是32位了。这个32位数据正是因为一次更新事件连续两次进入中断服务程序,两次发送SPI数据。那为什么完全相同的代码在FLASH里运行没这个问题呢,因为代码在DTCM的运行速率要比在FLASH里快,尽管在清中断请求标志的代码后面已经有了兼具延时功能的那句针对SPI数据寄存器的赋值语句,在退出中断前该请求标志位还是未完成清零而再进了一次中断。
看来,这里还得稍微加多点延时以保证中断请求标志在退出中断前被清零。为了避免加延时代码的盲目性,即要么短了要么长了,我们可以使用对标志位的轮询方式,将代码稍加改动变成下面的样子。
之后,再行验证测试都是正常的。若有兴趣的话,可以在清标志位的代码后面加DSB指令验证测试下。
OK,该话题今天就聊到这里,平时做应用开放时稍加留意即可。祝君好运~~!
===================================
往期话题阅读链接【点击即可阅读】: